package com.hmdp.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.hmdp.utils.RedisIdWork;
import com.hmdp.utils.SaveDelayCloseRequestToBackup;
import com.hmdp.utils.SaveSeckillRequestToBackup;
import com.hmdp.utils.UserHolder;
import com.sun.tools.javac.Main;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * 用户抢购优惠券，创建订单信息（RocketMQ版）
 */
@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Autowired
    private RedisIdWork redisIdWork;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RocketMQTemplate rocketMQTemplate; // 注入RocketMQ模板
    @Autowired
    private ISeckillVoucherService seckillVoucherService;
    @Autowired
    private SaveSeckillRequestToBackup saveSeckillRequestToBackup;
    @Autowired
    private SaveDelayCloseRequestToBackup saveDelayCloseRequestToBackup;

    // 配置初始化lua脚本（保留原有逻辑，不修改）
    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
    // RocketMQ主题（自定义，需提前创建或自动创建）
    public static final String SECKILL_ORDER_TOPIC = "seckill_order_topic";
    // 新增：延迟关单消息主题
    public static final String SECKILL_ORDER_CLOSE_DELAY_TOPIC = "seckill_order_close_delay_topic";
    // 新增：RocketMQ延迟等级（15分钟，需匹配RocketMQ配置的延迟等级）
// RocketMQ默认延迟等级：1=1s,2=5s,3=10s,4=30s,5=1m,6=2m,7=3m,8=4m,9=5m,10=6m,11=7m,12=8m,13=9m,14=10m,15=20m,16=30m,17=1h,18=2h
    private static final int DELAY_LEVEL_15MIN = 16; // 此处延时30分钟，若需精准15分钟，需自定义RocketMQ延迟等级

    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

    private IVoucherOrderService proxy;

    /**
     * 优惠券秒杀核心逻辑（修改：Lua通过后发送RocketMQ消息）
     */
    @Override
    public Result seckillVoucher(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        // 1. 生成订单id（保留原有逻辑）
        long orderId = redisIdWork.nextId("order");
        // 2. 执行lua脚本（校验库存、重复下单）
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(),
                userId.toString(),
                String.valueOf(orderId)
        );
        // 3. Lua脚本返回非0，说明校验失败
        if (result != 0) {
            return Result.fail(result == 1 ? "库存不足" : "不能重复下单");
        }
        // 4. 获取代理对象
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        // 5. 封装订单信息，发送到RocketMQ
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(orderId);
        voucherOrder.setUserId(userId);
        voucherOrder.setVoucherId(voucherId);
        // 6. 发送消息到RocketMQ（异步）
        try {
            // 异步回调发送MQ消息，设置生产者重试三次，三次失败进入兜底逻辑
            rocketMQTemplate.asyncSend(SECKILL_ORDER_TOPIC, voucherOrder, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    log.info("订单{}异步发送MQ成功，msgId：{}", orderId, sendResult.getMsgId());
                }
                @Override
                public void onException(Throwable e) {
                    log.error("订单{}异步发送MQ失败", orderId, e);
                    // 调用降级兜底方法（见第二层）
                    saveSeckillRequestToBackup.saveSeckillRequestToRedis(orderId, voucherOrder);
                }
            });
            // ========== 新增：异步回调发送延迟关单消息 ==========
            try {
                Message<VoucherOrder> delayMessage = MessageBuilder
                        .withPayload(voucherOrder)
                        .setHeader(RocketMQHeaders.KEYS, String.valueOf(orderId)) // 消息KEY=订单ID，后续删除用
                        .build();

                rocketMQTemplate.asyncSend(
                        SECKILL_ORDER_CLOSE_DELAY_TOPIC, // 1. 延迟关单主题
                        delayMessage,                    // 2. 消息体（VoucherOrder对象）
                        new SendCallback() {             // 3. 异步回调（成功/失败处理）
                            @Override
                            public void onSuccess(SendResult sendResult) {

                                log.info("订单{}延迟关单消息发送成功，msgId：{}", orderId, sendResult.getMsgId());
                            }
                            @Override
                            public void onException(Throwable e) {
                                log.error("订单{}延迟关单消息发送失败", orderId, e);
                                saveDelayCloseRequestToBackup.saveDelayCloseRequestToRedis(orderId, voucherOrder);
                            }
                        },
                        5000,                     // 4. 发送超时时间（5秒）5秒内响应回调结果
                        DELAY_LEVEL_15MIN                // 5. 延迟等级（15分钟）
                );
            } catch (Exception delayE) {
                log.error("订单{}延迟关单消息发送失败", orderId, delayE);
                saveDelayCloseRequestToBackup.saveDelayCloseRequestToRedis(orderId, voucherOrder);
            }
        } catch (Exception e) {
            log.error("订单{}发送MQ时主线程异常", orderId, e);
            saveSeckillRequestToBackup.saveSeckillRequestToRedis(orderId, voucherOrder);
            return Result.fail("下单请求已提交，请稍后查看订单状态");
        }
        // 7. 返回订单id
        return Result.ok(orderId);
    }

    /**
     * 创建订单（数据库操作，保留原有事务逻辑，不修改）
     */
    @Transactional
    public void createVoucherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
        Long voucherId = voucherOrder.getVoucherId();
        // 1. 校验是否重复下单
        Integer count = query()
                .eq("voucher_id", voucherId)
                .eq("user_id", userId)
                .count();
        if (count > 0) {
            log.info("用户{}已抢购过优惠券{}，订单id：{}", userId, voucherId, voucherOrder.getId());
            return;
        }
        // 2. 扣减库存（乐观锁防超卖）
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .gt("stock", 0)
                .update();
        if (!success) {
            log.error("优惠券{}库存不足，订单id：{}", voucherId, voucherOrder.getId());
            return;
        }
        // 3. 保存订单
        save(voucherOrder);
        log.info("订单{}创建成功，用户{}，优惠券{}", voucherOrder.getId(), userId, voucherId);
    }

    /**
     * 前端发起支付请求
     * @param orderId
     * @return
     */
    @Override
    public Result createPayRequest(Long orderId) {
        // ========== 1. 发起支付（前端点击支付时调用） ==========
        String lockKey = "seckill:pay:lock:" + orderId;
        RLock lock = redissonClient.getLock(lockKey);
        boolean lockSuccess = false;
        try {
            // tryLock参数说明：等待时间（0秒）、锁过期时间（3秒）、时间单位
            lockSuccess = lock.tryLock(0, 3, TimeUnit.SECONDS);
            if (!lockSuccess) {
                throw new RuntimeException("请勿重复发起支付，请稍后再试");
            }
            // 校验订单状态（仅未支付可发起支付）
            VoucherOrder order = getById(orderId);
            if (order == null) {
                throw new RuntimeException("订单不存在");
            }
            if (order.getStatus() != 0) {
                throw new RuntimeException("订单已支付/已关闭，无法发起支付");
            }

            // Redis记录“支付中”状态（供前端轮询）
            String payStatusKey = "seckill:pay:status:" + orderId;
            stringRedisTemplate.opsForValue().set(payStatusKey, "paying", 15, TimeUnit.MINUTES);

            // 模拟调用支付平台（替换为真实微信/支付宝SDK）
            String payParams = "https://your-pay-platform.com/qrcode?orderId=" + orderId + "&payNo=" + IdUtil.simpleUUID();
            log.info("订单{}发起支付成功，支付参数：{}", orderId, payParams);
            return Result.ok(payParams);

        } catch (InterruptedException e) {
            // 捕获锁等待中断异常
            log.error("获取支付分布式锁中断", e);
            throw new RuntimeException("发起支付失败，请稍后再试");
        } finally {
            // ========== 3. 释放Redisson锁（关键：先判断是否持有锁，再释放） ==========
            if (lockSuccess && lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.info("订单{}支付锁已释放", orderId);
            }
        }
    }

    /**
     * 处理订单信息（保留原有逻辑，不修改）
     */
    public void handleVoucherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
        // 分布式锁防重复下单（保留原有Redisson锁）
        RLock lock = redissonClient.getLock("order:" + userId);
        boolean isLock = lock.tryLock();
        if (!isLock) {
            log.error("用户{}不允许重复下单，订单id：{}", userId, voucherOrder.getId());
            return;
        }
        try {
            // 调用事务方法创建订单,进行一人一单的校验
            proxy.createVoucherOrder(voucherOrder);
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    // ========== 新增：2. 处理支付回调（支付平台调用） ==========
    @Transactional(rollbackFor = Exception.class)
    public boolean handlePayCallback(Long orderId, String payNo) {
        // 1. Redis幂等校验：防止支付平台重复回调
        String idempotentKey = "seckill:pay:idempotent:" + payNo;
        Boolean idempotentSuccess = stringRedisTemplate.opsForValue().setIfAbsent(idempotentKey, "1", 24, TimeUnit.HOURS);
        if (!idempotentSuccess) {
            log.info("支付回调幂等校验：流水号{}已处理", payNo);
            return true;
        }
        try {
            // 2. 乐观锁更新订单状态（仅未支付时更新）
            boolean updateSuccess = update()
                    .set("status", 1) // 改为已支付
                    .set("pay_no", payNo)
                    .set("pay_time", LocalDateTime.now())
                    .eq("id", orderId)
                    .eq("status", 0) // 防并发修改
                    .update();

            if (updateSuccess) {
                // 3. 更新Redis支付状态
                String payStatusKey = "seckill:pay:status:" + orderId;
                stringRedisTemplate.opsForValue().set(payStatusKey, "success", 24, TimeUnit.HOURS);

                // 4. 删除延迟关单消息（核心联动）
                deleteDelayCloseMessage(orderId);
                log.info("订单{}支付成功，已更新状态并删除延迟关单消息", orderId);
                return true;
            } else {
                log.error("订单{}支付成功，但状态更新失败", orderId);
                return false;
            }
        } catch (Exception e) {
            log.error("支付回调处理异常", e);
            return false;
        }
    }

    // ========== 新增：3. 查询支付状态（前端轮询调用） ==========
    public Result getPayStatus(Long orderId) {
        String payStatusKey = "seckill:pay:status:" + orderId;
        String status = stringRedisTemplate.opsForValue().get(payStatusKey);
        // Redis无数据：查数据库兜底
        if (status == null) {
            VoucherOrder order = getById(orderId);
            if (order == null) {
                return Result.fail("invalid"); // 订单不存在
            }
            return switch (order.getStatus()) {
                case 1 -> Result.ok("success"); // 已支付
                case 2 -> Result.fail("closed"); // 已关闭
                default -> Result.fail("pending"); // 未支付
            };
        }
       return Result.ok(status);
    }

    // ========== 新增：4. 删除延迟关单消息（支付成功联动） ==========
    private void deleteDelayCloseMessage(Long orderId) {
        String namesrvAddr = "192.168.111.100:9876";
        String topic = SECKILL_ORDER_CLOSE_DELAY_TOPIC;
        String key = orderId.toString();

        // 构建 mqadmin 删除消息命令
        String[] cmd = new String[]{
                "deleteMessage",
                "-n", namesrvAddr,
                "-t", topic,
                "-k", key
        };
        try {
            // 执行 mqadmin 命令
            Main.main(cmd);
            log.info("订单{}延迟关单消息已通过 mqadmin 删除", orderId);
        } catch (Exception e) {
            log.warn("订单{}删除延迟消息失败（消息可能已消费）", orderId, e);
        }
    }

    // ========== 1. 关单外层方法（仅负责分布式锁，和handleVoucherOrder对齐） ==========
    public void handleCloseOrder(VoucherOrder voucherOrder) throws InterruptedException {
        Long orderId = voucherOrder.getId();
        // 分布式锁：锁粒度=订单ID（关单针对单个订单，比用户ID更精准）
        RLock lock = redissonClient.getLock("seckill:order:close:" + orderId);
        // 和创建订单的锁超时逻辑对齐（创建订单是无参tryLock，这里保留3秒等待+10秒持有，也可改为无参）
        boolean isLock = lock.tryLock();
        if (!isLock) {
            log.warn("订单{}关单：获取分布式锁失败，跳过", orderId);
            return;
        }
        try {
            // 调用内层事务方法（必须通过代理对象，避免事务失效）
            proxy.doCloseOrder(voucherOrder);
        } finally {
                lock.unlock();
                log.info("订单{}关单：释放分布式锁", orderId);
        }
    }

    // ========== 2. 关单内层事务方法（核心数据库操作，加@Transactional，和createVoucherOrder对齐） ==========
    @Transactional // 异常时回滚所有数据库操作
    public void doCloseOrder(VoucherOrder voucherOrder) {
        Long orderId = voucherOrder.getId();
        Long voucherId = voucherOrder.getVoucherId();

        // 1. 幂等校验：查询订单状态（仅未支付时执行关单）
        VoucherOrder order = getById(orderId);
        if (order == null || order.getStatus() != 0) { // 0=未支付
            log.info("订单{}非未支付状态（状态={}），无需关单", orderId, order == null ? "不存在" : order.getStatus());
            return;
        }

        // 2. 更新订单状态为已关闭（乐观锁防并发）
        boolean updateOrder = update()
                .set("status", 4) // 4=已关闭
                .eq("id", orderId)
                .eq("status", 0) // 仅未支付时更新，保证幂等
                .update();
        if (!updateOrder) {
            log.warn("订单{}关单：更新状态失败（可能已被并发修改）", orderId);
            // 抛出异常触发事务回滚（避免库存恢复但订单状态未更新）
            throw new RuntimeException("订单状态更新失败，关单回滚");
        }

        // 3. 恢复优惠券库存（乐观锁防超发）
        boolean restoreStock = seckillVoucherService.update()
                .setSql("stock = stock + 1")
                .eq("voucher_id", voucherId)
                .update();
        if (!restoreStock) {
            log.error("订单{}关单：恢复库存失败", orderId);
            // 抛出异常触发事务回滚（订单状态更新也会回滚）
            throw new RuntimeException("库存恢复失败，关单回滚");
        }

        log.info("订单{}关单成功：已关闭订单+恢复优惠券{}库存", orderId, voucherId);
    }

}









